#region Using Directives
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
#endregion Using Directives

namespace Minimal.Web
{
    public partial class QueryString
    {
        /// <summary>
        /// Handles the writing and reading of values to fields or parameters
        /// that have been tagged with the <see cfef="QueryStringParameterAttribute" />.
        /// </summary>
        private abstract class ParameterHandler
        {
            #region Fields
            /// <summary>
            /// The key of the QueryStringParameter that this instance handles.
            /// </summary>
            public readonly string Key;

            /// <summary>
            /// A type converter used to convert to and from the query string
            /// representation an the native type of the field of property. Also
            /// used for setting the default value from the attribute if the default
            /// value specified is not of the same type as the member it decorates.
            /// </summary>
            private TypeConverter typeConverter;

            /// <summary>
            /// The default value of the parameter, the actual value, not the string
            /// representation.
            /// </summary>
            private object defaultValue;
            #endregion

            #region Constructor
            [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors",
                    Justification = "The called methods are template methods and not dependant on instance fields.")]
            protected ParameterHandler(QueryStringParameterAttribute attribute, MemberInfo i)
            {
                this.Key = attribute.Key;
                this.typeConverter = this.GetTypeConverter(attribute, i);
                this.defaultValue = this.GetDefaultValue(attribute, i);

                string defaultValue = this.defaultValue as string;
                if (defaultValue != null) this._defaultValueIsEmpty = string.IsNullOrEmpty(defaultValue);
                else this._defaultValueIsEmpty = this.defaultValue == null;
            }
            #endregion

            #region GetTypeConverter
            /// <summary>
            /// Gets the TypeConverter this handler uses.
            /// </summary>
            /// <param name="attribute">The attribute used to create this handler.</param>
            /// <param name="i">The field or property the attribute decorates.</param>
            /// <returns>A TypeConverter.</returns>
            protected TypeConverter GetTypeConverter(QueryStringParameterAttribute attribute, MemberInfo i)
            {
                string converterName =
                    (from a in i.GetCustomAttributes(typeof(TypeConverterAttribute), true).Cast<TypeConverterAttribute>()
                     select a.ConverterTypeName).FirstOrDefault();

                if (converterName != null)
                {
                    return (TypeConverter)Activator.CreateInstance(Type.GetType(converterName), true);
                }
                else
                {
                    return TypeDescriptor.GetConverter(this.GetMemberType(i));
                }
            }
            #endregion

            #region GetDefaultValue
            /// <summary>
            /// Gets the value to use as default value.
            /// </summary>
            /// <param name="attribute">The attribute used to create the handler.</param>
            /// <param name="i">The field or property the attribute decorates.</param>
            /// <returns>The default value.</returns>
            protected object GetDefaultValue(QueryStringParameterAttribute attribute, MemberInfo i)
            {
                Type memberType = this.GetMemberType(i);
                if (attribute.DefaultValue != null)
                {
                    if (memberType.IsAssignableFrom(attribute.DefaultValue.GetType()))
                    {
                        return attribute.DefaultValue;
                    }
                    else
                    {
                        return this.typeConverter.ConvertFrom(attribute.DefaultValue);
                    }
                }
                else
                {
                    if (memberType.IsValueType)
                    {
                        return Activator.CreateInstance(memberType, true);
                    }
                    else
                    {
                        return null;
                    }
                }
            }
            #endregion

            #region HasDefaultValue
            /// <summary>
            /// True if the value of the member this handler handles
            /// on the specified object is the default value of the member.
            /// </summary>
            /// <param name="o">The object to get the value from.</param>
            /// <returns>True if the value is the default value of the handler.</returns>
            public bool HasDefaultValue(object o)
            {
                return object.Equals(this.defaultValue, this.GetValue(o));
            }
            #endregion

            #region DefaultValueIsEmpty
            [DebuggerBrowsable(DebuggerBrowsableState.Never)]
            private bool _defaultValueIsEmpty;

            /// <summary>
            /// True if the default value is null or an empty string.
            /// </summary>
            public bool DefaultValueIsEmpty
            {
                [DebuggerStepThrough]
                get
                {
                    return this._defaultValueIsEmpty;
                }
            }
            #endregion

            #region SetDefaultValue
            /// <summary>
            /// Sets the default value of the handler to the specified object.
            /// </summary>
            /// <param name="o">The object to set the default value to.</param>
            public void SetDefaultValue(object o)
            {
                this.SetMemberValue(o, this.defaultValue);
            }
            #endregion

            #region GetStringValue
            /// <summary>
            /// Get the value of the member this handler handles from
            /// the specified object and converts it to its string representation.
            /// </summary>
            /// <param name="o">The object to get the value from.</param>
            /// <returns>A string.</returns>
            public string GetStringValue(object o)
            {
                return this.typeConverter.ConvertToString(null, CultureInfo.InvariantCulture, this.GetMemberValue(o));
            }
            #endregion

            #region SetValueFromString
            /// <summary>
            /// Sets the value of the member this handler handles on the
            /// specified object from the string representation of the value.
            /// </summary>
            /// <param name="o">The object to set the value to.</param>
            /// <param name="s">A string representation the value to set.</param>
            public void SetValueFromString(object o, string s)
            {
                this.SetMemberValue(o, this.typeConverter.ConvertFrom(null, CultureInfo.InvariantCulture, s));
            }
            #endregion

            #region SetValue
            /// <summary>
            /// Sets the specifed value to the member this handler handles
            /// on the object specified in the <paramref name="o"/> parameter.
            /// </summary>
            /// <param name="o">The object to set the value to.</param>
            /// <param name="value">The value to set to the object.</param>
            public void SetValue(object o, object value)
            {
                this.SetMemberValue(o, value);
            }
            #endregion

            #region Abstract methods
            protected abstract object GetMemberValue(object o);
            protected abstract void SetMemberValue(object o, object value);
            protected abstract Type GetMemberType(MemberInfo i);
            #endregion

            #region Create
            /// <summary>
            /// Creates a new QueryStringParameterHandler.
            /// </summary>
            /// <param name="attribute">The attribute used for creating the handler.</param>
            /// <param name="i">The property- or field-info the attribute decorates.</param>
            /// <returns>A handler.</returns>
            [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
            public static ParameterHandler Create(QueryStringParameterAttribute attribute, MemberInfo i)
            {
                if (i is PropertyInfo)
                {
                    return new PropertyParameterHandler(attribute, (PropertyInfo)i);
                }
                else if (i is FieldInfo)
                {
                    return new FieldParameterHandler(attribute, (FieldInfo)i);
                }
                else
                {
                    throw new ArgumentException(QueryStringResources.CreateParameterHandlerMemberInfoTypeException);
                }
            }
            #endregion Create

            #region GetValue
            /// <summary>
            /// Gets the value of the member this handler handles from
            /// the specified object.
            /// </summary>
            /// <param name="o">The object to get the value from.</param>
            /// <returns>The value.</returns>
            public object GetValue(object o)
            {
                return this.GetMemberValue(o);
            }
            #endregion
        }
    }
}
